#include "JazzyJson.hpp"

#include <istream>

namespace Jazzy {

    /// All syntactic elements we choose to stop on.
    struct Syntax { enum Enum {
        ObjectOpening,
        ObjectClosure,
        FieldName,
        FieldValue,
        ArrayOpening,
        ArrayClosure,
        ArrayElement,
        TOTAL
    };};

    class JsonReader::Private : private JsonReader
    {
    public:
        static const char OBJECT_START      = '{';
        static const char OBJECT_END        = '}';
        static const char ARRAY_START       = '[';
        static const char ARRAY_END         = ']';
        static const char ELEMENT_SEPARATOR = ',';
        
        
        static bool evaluateChar(JsonReader * pReader, char c) {
            Node & node = pReader->m_node;
            
            bool success = true;
            if (node.m_parsingContext.empty()) {
                success = Private::parseValue(pReader, c);
            }
            else {
                switch (node.m_parsingContext.back())
                {
                    case Syntax::ObjectOpening:  success = Private::beginObjectContent(pReader, c);     break;
                    case Syntax::ObjectClosure:  success = Private::endObjectContent(pReader, c);       break;
                    case Syntax::FieldName:      success = Private::beginFieldValue(pReader, c);        break;
                    case Syntax::FieldValue:     success = Private::endFieldValue(pReader, c);          break;
                    case Syntax::ArrayOpening:   success = Private::beginArrayContent(pReader, c);      break;
                    case Syntax::ArrayClosure:   success = Private::endArrayContent(pReader, c);        break;
                    case Syntax::ArrayElement:   success = Private::endArrayElement(pReader, c);        break;
                }
            }
            return success;
        }
        
        static bool isStructureBondary( Syntax::Enum syntax ) 
        {
            switch(syntax)
            {
                case Syntax::ObjectOpening:
                case Syntax::ObjectClosure:
                case Syntax::ArrayOpening:
                case Syntax::ArrayClosure:
                    return true;
                default:
                    return false;
            }
        }
        
        
        static Settings & getDefaultSettings() { return s_defaultSettings; }
        
        class OnError {
            friend struct ErrorHandler;
            static void crash(void *, char const * errorMessage) { char p = *(char *)0; char o = p; p = o; }  
        };


    private:
        static bool beginObjectContent(JsonReader * pReader, char c) {
            std::istream & in = *pReader->m_pStream;
            if (in.eof())        { return pReader->m_settings.getErrorHandler()("Incomplete object. A '}' was expected.");}
            else if (!in.good()) { return pReader->m_settings.getErrorHandler()("Encountered a problem reading object content."); }

            bool success = true;
            Node & node = pReader->m_node;
            switch(c)
            {
            case OBJECT_END:
                node.m_parsingContext.pop_back();
                setTextAndTypeAndSyntax(pReader, OBJECT_END, Type::Object, Syntax::ObjectClosure);
                break;
            case '\"':
                node.m_parsingContext.push_back(Syntax::FieldName);
                success = readString(pReader);
                break;
            default: return pReader->m_settings.getErrorHandler()("Invalid object members. Was expecting either a string, or a '}'."); break;
            }
            return success;
        }

        static bool endObjectContent(JsonReader * pReader, char c)
        {
            pReader->m_node.m_parsingContext.pop_back();
            return pReader->m_pStream->eof() || evaluateChar(pReader, c);
        }

        static bool beginArrayContent(JsonReader * pReader, char c)
        {
            std::istream & in = *pReader->m_pStream;
            if (in.eof())   { return pReader->m_settings.getErrorHandler()("Incomplete array. Was expecting a value or a ']'");}
            if (!in.good()) { return pReader->m_settings.getErrorHandler()("Encountered a problem reading array content"); }

            Node & node = pReader->m_node;

            switch(c) {
            case ARRAY_END:
                node.m_parsingContext.pop_back();
                setTextAndTypeAndSyntax(pReader, ARRAY_END, Type::Array, Syntax::ArrayClosure);
                break;
            default:
                node.m_parsingContext.push_back(Syntax::ArrayElement);
                return parseValue(pReader, c);
            }
            return true;
        }

        static bool endArrayContent(JsonReader * pReader, char c)
        {
            pReader->m_node.m_parsingContext.pop_back();
            return pReader->m_pStream->eof() || evaluateChar(pReader, c);
        }

        static bool parseValue(JsonReader* pReader, char c) 
        {
            std::istream & in = *pReader->m_pStream;
            if (in)
            {
                switch(c) {
                case OBJECT_START : return readObject(pReader);       break;
                case ARRAY_START  : return readArray(pReader);        break;
                case '\"'         : return readString(pReader);       break;
                case 't'          : return readTrue(pReader);         break;
                case 'f'          : return readFalse(pReader);        break;
                case 'n'          : return readNull(pReader);         break;
                default  :
                    in.clear(in.rdstate() &~std::ios_base::eofbit);
                    in.putback(c);
                    return readNumber(pReader);
                    break;
                }
            }
            else { return pReader->m_settings.getErrorHandler()("Could not read value from stream."); }
            return true;
        }

        static bool beginFieldValue( JsonReader* pReader, char c ) 
        {
            std::istream & in = *pReader->m_pStream;
            if (in.eof())        { return pReader->m_settings.getErrorHandler()("Incomplete field value. A string, number, boolean, null, object or array was expected.");}
            else if (!in.good()) { return pReader->m_settings.getErrorHandler()("Encountered a problem reading field value."); }

            if (!isCharInString(c, ":=")) { return pReader->m_settings.getErrorHandler()("A field separator (':' or '=') is required between a field name and a field value."); }

            while (in.get(c).good() && isspace(c));
            if (in.eof())               { return pReader->m_settings.getErrorHandler()("Incomplete field value. A string, number, boolean, null, object or array was expected");}
            else if (!in.good())        { return pReader->m_settings.getErrorHandler()("Encountered a problem reading field value."); }

            Node & node = pReader->m_node;
            node.m_parsingContext.back() = Syntax::FieldValue;
            return parseValue(pReader, c);
        }

        static bool endFieldValue( JsonReader* pReader, char c ) 
        {
            std::istream & in = *pReader->m_pStream;
            if (in.fail()) { return pReader->m_settings.getErrorHandler()("Encountered a problem reading field value."); }

            Node & node = pReader->m_node;

            if (c == OBJECT_END) {
                node.m_parsingContext.pop_back();
                node.m_parsingContext.pop_back();
                setTextAndTypeAndSyntax(pReader, OBJECT_END, Type::Object, Syntax::ObjectClosure);
            }
            else {
                if (in.eof()) { return pReader->m_settings.getErrorHandler()("Incomplete object ending. A '}' was expected.");}

                if (c == ',')
                {
                    while (in.get(c).good() && isspace(c));
                    if (in.eof())        { return pReader->m_settings.getErrorHandler()("Incomplete object. A string was expected.");}
                    else if (!in.good()) { return pReader->m_settings.getErrorHandler()("Encountered a problem reading field name."); }
                }
                else if (pReader->m_settings.isCommaMandatoryBetweenFields())
                {
                    return pReader->m_settings.getErrorHandler()("Missing a comma (',') between field name and field value.");
                }

                if (c != '"') { return pReader->m_settings.getErrorHandler()("Incorrect field name. A string was expected"); }

                node.m_parsingContext.back() = Syntax::FieldName;
                return readString(pReader);
            }
            return true;
        }

        static bool endArrayElement( JsonReader* pReader, char c ) 
        {
            std::istream & in = *pReader->m_pStream;
            if (in.eof())               { return pReader->m_settings.getErrorHandler()("Incomplete object ending. Expecting either a ',', a ';', or a '}'");}
            else if (!in.good())        { return pReader->m_settings.getErrorHandler()("Encountered a problem reading array element."); }

            Node & node = pReader->m_node;
            bool success = true;
            if (c == ARRAY_END) {
                node.m_parsingContext.pop_back();
                node.m_parsingContext.pop_back();
                setTextAndTypeAndSyntax(pReader, ARRAY_END, Type::Array, Syntax::ArrayClosure);
            }
            else
            {
                if (c != ELEMENT_SEPARATOR) { return pReader->m_settings.getErrorHandler()("Expected ',' to separate array elements."); }

                while (in.get(c).good() && isspace(c));
                if (in.eof())               { return pReader->m_settings.getErrorHandler()("Incomplete object ending. Expecting either a value after the ','");}
                else if (!in.good())        { return pReader->m_settings.getErrorHandler()("Encountered a problem reading array element."); }

                success = parseValue(pReader, c);
            }
            return success;
        }
        
        static void setTextAndTypeAndSyntax(JsonReader * pReader, char text, Type::Enum type, Syntax::Enum syntax) {
            Node & node = pReader->m_node;
            node.m_text.clear();
            node.m_text.reserve(2);
            node.m_text.push_back(text);
            node.m_text.push_back('\0');
            node.m_type = type;
            node.m_parsingContext.push_back(syntax);
        }

        // Accumulate the string in the node text
        static bool readString(JsonReader * pReader) {
            Node & node = pReader->m_node;
            std::istream & stream = *pReader->m_pStream;
            node.m_text.clear();

            char c;
            while(stream.get(c).good() && c != '\"')
            {
                if (c == '\\' && stream.get(c).good())
                {
                    switch(c)
                    {
                    case '\\': node.m_text.push_back('\\'); break;
                    case '/': node.m_text.push_back('/'); break;
                    case 'b': node.m_text.push_back('\b'); break;
                    case 'f': node.m_text.push_back('\f'); break;
                    case 'n': node.m_text.push_back('\n'); break;
                    case 'r': node.m_text.push_back('\r'); break;
                    case 't': node.m_text.push_back('\t'); break;
                    case 'u': // Numeral unicode character
                        {
                            char numberString[] = "x0000";
                            for (int i = 1; i < 5; ++i)
                            {
                                stream.get(numberString[i]);
                                if (!stream.good() || !isxdigit(numberString[i])) { return pReader->m_settings.getErrorHandler()("An Hex digit was expected"); break; }
                            }
                            // TODO: convert the number into a unicode character
                            node.m_text.push_back('?');
                            break;
                        }
                    default:
                        return pReader->m_settings.getErrorHandler()("Unknown escape sequence"); break;
                    }
                }
                else
                {
                    node.m_text.push_back(c);
                }
            }

            if (!stream.good()) return pReader->m_settings.getErrorHandler()("A problem occurred before parsing the closing '\"' character");

            node.m_text.push_back('\0');
            node.m_type = Type::String;
            return true;
        }

        template< typename vector >
        static bool matchText(std::istream & in, char const * pText, int size, vector & accumulator)
        {
            accumulator.clear();
            accumulator.reserve(size);

            // consider the 1st char already matches
            accumulator.push_back(pText[0]);
            bool stillMatches = true;
            char c = 0;
            int const lastChar = size - 1;
            for (int i = 1; stillMatches && in.get(c).good() && i<lastChar; ++i)
            {
                stillMatches = c == pText[i];
                accumulator.push_back(c);
            }

            if (!in.fail()) {
                in.clear(in.rdstate() &~std::ios_base::eofbit);
                in.putback(c);
            }

            accumulator.push_back(pText[lastChar]);
            return stillMatches;
        }

        static bool readTrue(JsonReader * pReader) {
            static const char text[] = "true";
            Node & node = pReader->m_node;
            node.m_boolean = matchText(*pReader->m_pStream, text, sizeof(text), node.m_text);
            if (!node.m_boolean) { return pReader->m_settings.getErrorHandler()("The word 'true' was expected."); }
            node.m_type = Type::Boolean;
            return true;
        }


        static bool readFalse(JsonReader * pReader) {
            static const char text[] = "false";
            Node & node = pReader->m_node;
            node.m_boolean = !matchText(*pReader->m_pStream, text, sizeof(text), node.m_text);
            if (node.m_boolean) { return pReader->m_settings.getErrorHandler()("The word 'false' was expected."); }
            node.m_type = Type::Boolean;
            return true;
        }

        static bool readNull(JsonReader * pReader) {
            static const char text[] = "null";
            Node & node = pReader->m_node;
            bool const result = matchText(*pReader->m_pStream, text, sizeof(text), node.m_text);
            if (result) {
                node.m_type = Type::Null;
            }
            else { return pReader->m_settings.getErrorHandler()("The word 'null' was expected."); }
            return true;
        }

        template< typename Array >
        static bool isCharInString(char const c, const Array & array)
        {
            bool result = c == array[0];
            for(int i = 1; !result && i < sizeof(Array); ++i)
            {
                result = c == array[i];
            }
            return result;
        }

        static bool isNumericValue(char c)          { return isCharInString(c, "0123456789+-.eE"); }
        static bool isStringBeginning(char const c) { return c == '\"'; }
        static bool isNumberBeginning(char const c) { return isCharInString(c, "-0123456789."); }

        static bool readNumber(JsonReader * pReader) {
            std::istream & in = *pReader->m_pStream;
            Node & node = pReader->m_node;
            node.m_text.clear();
            char c;
            while(in.get(c).good() && isNumericValue(c)) {
                node.m_text.push_back(c);
            }

            if (!in.good()) pReader->m_settings.getErrorHandler()("Badly formed number");

            in.clear(in.rdstate() &~std::ios_base::eofbit);
            in.putback(c);
            node.m_text.push_back('\0');

            int success = sscanf(&(*node.m_text.begin()), "%lf", &node.m_number);
            if (success != 1) { return pReader->m_settings.getErrorHandler()("Badly formed number"); }

            node.m_type = Type::Number;
            return true;
        }

        static bool readObject( JsonReader* pReader ) {
            setTextAndTypeAndSyntax(pReader, OBJECT_START, Type::Object, Syntax::ObjectOpening);
            return true;
        }

        static bool readArray ( JsonReader* pReader ) {
            setTextAndTypeAndSyntax(pReader, ARRAY_START, Type::Array, Syntax::ArrayOpening);
            return true;
        }

        static Settings s_defaultSettings;
     };

    JsonReader::Settings JsonReader::Private::s_defaultSettings;

    JsonReader::JsonReader()
        : m_pStream(0)
        , m_settings(Private::getDefaultSettings())
    {
    }

    JsonReader::JsonReader( std::istream & stream )
        : m_pStream(&stream)
        , m_settings(Private::getDefaultSettings())
    {
    }

    void JsonReader::setDocument( std::istream & stream )
    {
        m_pStream = &stream;
    }


    bool JsonReader::read()
    {
        if (!m_pStream) return false;

        std::istream & in = *m_pStream;

        if (!in.eof()) {
        char c; while(in.get(c).good() && isspace(c));
            if (!in.eof()) {
                return Private::evaluateChar(this, c);
            }
        }
        
        {
            if (m_node.m_parsingContext.empty()) {
                return m_settings.getErrorHandler()("read() was called while it was already passed the end of the document.");
            }
            m_node.m_parsingContext.pop_back();
            m_node.m_text.clear();
            m_node.m_text.push_back('\0');
            if (!m_node.m_parsingContext.empty()) {
                return m_settings.getErrorHandler()("Unexpected end of file.");
            }
            return false;
        }
    }

    JsonReader::Type::Enum JsonReader::getValueType() const {
        return m_node.m_type;
    }

    bool JsonReader::isObjectOpening() const {
        return !m_node.m_text.empty() && m_node.m_text[0] == Private::OBJECT_START;
    }

    bool JsonReader::isFieldName() const {
        return !m_node.m_parsingContext.empty() && m_node.m_parsingContext.back() == Syntax::FieldName;
    }

    bool JsonReader::isFieldValue() const {
        return m_node.m_parsingContext.back() == Syntax::FieldValue ||
            (m_node.m_parsingContext.size() >= 2 && Private::isStructureBondary(Syntax::Enum(m_node.m_parsingContext.back())) && m_node.m_parsingContext[m_node.m_parsingContext.size() - 2] == Syntax::FieldValue);
    }

    bool JsonReader::isObjectClosure() const {
        return !m_node.m_text.empty() && m_node.m_text[0] == Private::OBJECT_END;
    }

    bool JsonReader::isArrayOpening() const {
        return !m_node.m_text.empty() && m_node.m_text[0] == Private::ARRAY_START;
    }

    bool JsonReader::isArrayElement() const {
        return !m_node.m_parsingContext.empty() && m_node.m_parsingContext.back() == Syntax::ArrayElement;
    }

    bool JsonReader::isArrayClosure() const
    {
        return !m_node.m_text.empty() && m_node.m_text[0] == Private::ARRAY_END;
    }
    
    
    char const * JsonReader::getValueAsString() const {
        return &(*const_cast< JsonReader * >(this)->m_node.m_text.begin());
    }
    
    double       JsonReader::getValueAsDouble() const {
        if (m_node.m_type != Type::Number) m_settings.getErrorHandler()("Trying to read a number out of a non-number value.");
        return m_node.m_number;
    }
    
    float        JsonReader::getValueAsFloat() const {
        if (m_node.m_type != Type::Number) m_settings.getErrorHandler()("Trying to read a number out of a non-number value.");
        return float(m_node.m_number);
    }

    
    int          JsonReader::getValueAsInt() const {
        if (m_node.m_type != Type::Number) m_settings.getErrorHandler()("Trying to read a number out of a non-number value.");
        return int(m_node.m_number);
    }

    
    bool         JsonReader::getValueAsBool() const {
        if (m_node.m_type != Type::Boolean) m_settings.getErrorHandler()("Trying to read a number out of a non-boolean value.");
        return m_node.m_boolean;
    }

    
    double       JsonReader::tryGetValue(double & value) const {
        if (m_node.m_type != Type::Number) return false;
        value = m_node.m_number;
        return true;
    }
    
    float        JsonReader::tryGetValue(float & value) const {
        if (m_node.m_type != Type::Number) return false;
        value = float(m_node.m_number);
        return true;
    }

    
    int          JsonReader::tryGetValue(int & value) const {
        if (m_node.m_type != Type::Number) return false;
        value = int(m_node.m_number);
        return true;
    }

    
    bool         JsonReader::tryGetValue(bool & value) const {
        if (m_node.m_type != Type::Boolean) return false;
        value = m_node.m_boolean;
        return true;
    }

    
    bool JsonReader::isNull() const { return m_node.m_type == Type::Null; }
    
    JsonReader::Settings & JsonReader::getDefaultSettings() {
        return Private::getDefaultSettings();
    }
    
    void JsonReader::setDefaultSettings(const Jazzy::JsonReader::Settings &settings) {
        Private::getDefaultSettings() = settings;
    }
    
    void JsonReader::setSettings(const Jazzy::JsonReader::Settings &settings) {
        m_settings = settings;
    }
    
    JsonReader::Settings & JsonReader::getSettings() { return m_settings; }
    
    JsonReader::Settings const & JsonReader::getSettings() const { return m_settings; }

    JsonReader::Node::Node()
    : m_type(Type::Null)
    {
    }
    
    JsonReader::ErrorHandler::ErrorHandler()
    : m_function(&Private::OnError::crash)
    , m_pValue(0)
    {}
    
    JsonReader::ErrorHandler::ErrorHandler(HandlerFunction function, void * value)
    : m_function(function ? function : &Private::OnError::crash)
    , m_pValue(value)
    {}
    
    bool JsonReader::ErrorHandler::operator() (char const * pMessage) const {
        m_function(m_pValue, pMessage);
        return false;
    }
    
    JsonReader::Settings::Settings()
    : m_errorHandler()
    , m_isCommaMandatoryBetweenFields(true)
    {}
    
    JsonReader::ErrorHandler const & JsonReader::Settings::getErrorHandler() const {
        return m_errorHandler;
    }
    
    JsonReader::Settings & JsonReader::Settings::setErrorHandler(ErrorHandler errorHandler) {
        m_errorHandler = errorHandler.m_function ? errorHandler : Private::getDefaultSettings().getErrorHandler();
        return *this;
    }
    
    bool JsonReader::Settings::isCommaMandatoryBetweenFields() const {
        return m_isCommaMandatoryBetweenFields;
    }
    
    JsonReader::Settings & JsonReader::Settings::setCommaMandatoryBetweenFields(bool value) {
        m_isCommaMandatoryBetweenFields = value;
        return *this;
    }
}
